package scheduler;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.TimerTask;

import environment.Application;
import environment.Environment;
import environment.PhysicalMachine;
import environment.VirtualMachine;

public class SchedulerTask extends TimerTask {

	public enum Mode {
	    GREEN, ORANGE, RED
	}
	
	private final int ttLow = 50;
	private final int ttHigh = 75;
	
	public static Mode mode = Mode.GREEN;
	
	private JMSAppListener appListener;
	
	// stats
	private Integer cpusOverallRunning;
	private Integer cpusMaxUsed;
	private Integer pmsRunning;
	
	private PhysicalMachine leastUsedPM = null;
	private Integer leastUsedPMcpus = null;
	private PrintWriter pw= null;
	private FileWriter fw=null;
	
	public SchedulerTask() throws Exception
	{
		appListener = new JMSAppListener();
		
		//statistic data output
		//try {
		//	fw = new FileWriter("statisticdata.csv");
		//} catch (IOException e) {
		//	// TODO Auto-generated catch block
		//	e.printStackTrace();
		//}
        //pw = new PrintWriter(fw);
	}
	
	@Override
	public void run() {
		
		Environment.actionCounter = 0;
		Environment.powerConsumption = 0.0;
		
		/* read from jms */
		try {
			appListener.listenApps();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		computeStats();
		
		float usagePercentage = getUsagePercentage(cpusMaxUsed, cpusOverallRunning);
		mode = getMode(usagePercentage);
		
		// move apps from federations to own environment
		if(mode == Mode.GREEN || mode == Mode.ORANGE)
		{
			moveAppsFromFederations();
			
			// adapt statistics
			computeStats();
		}
			
		usagePercentage = getUsagePercentage(cpusMaxUsed, cpusOverallRunning);
		mode = getMode(usagePercentage);
		
		// try to remove resources
		if(mode == Mode.GREEN)
		{
			if(pmsRunning > 1)
			{
				cpusOverallRunning = tryToMoveVM(leastUsedPM, cpusMaxUsed, 
						cpusOverallRunning);
				
				cpusOverallRunning = tryToShutDownVM(leastUsedPM, cpusMaxUsed, 
						cpusOverallRunning);
				
				cpusOverallRunning = tryToShutDownPM(leastUsedPM, cpusMaxUsed, 
						cpusOverallRunning);
			}				
		} 
		// try to add resources
		else if(mode == Mode.RED)
		{
			cpusOverallRunning = tryToStartVM(cpusMaxUsed, cpusOverallRunning);
			
			usagePercentage = getUsagePercentage(cpusMaxUsed, cpusOverallRunning);
			mode = getMode(usagePercentage);
			
			if(mode == Mode.RED)
				cpusOverallRunning = tryToStartPM(cpusMaxUsed, cpusOverallRunning);	
		}
		
		usagePercentage = getUsagePercentage(cpusMaxUsed, cpusOverallRunning);
		mode = getMode(usagePercentage);
		
		System.out.println("\nSCHEDULER: " + mode + " " + usagePercentage);	
		//System.out.println("\nACTIONCOUNTER: " + Environment.actionCounter);
		//Environment.powerConsumption += getPowerConsumption();
		//System.out.println("\nPOWERCONSUMPTION: " + Environment.powerConsumption);
		
		
		//statistic data output
        //Write to file for the first row
        
        //pw.println(mode + ";" + Math.round(usagePercentage)
        //		+";"+Environment.actionCounter
        //		+";"+Math.round(Environment.powerConsumption));
        
        //Flush the output to the file
        //pw.flush();
	}	
	
	/*
	pm 	350 W
	vm	10 W
	app	0,1 W

	move app 	0,5 W
	move vm	 	5 W
	config vm	2 W
	create vm	10 W
	start pm	50 W
	shutdown vm 	7 W
	shutdown pm	20 W
	federations	0,4 W
	*/
	private double getPowerConsumption()
	{
		double consumption = 0;
		
		for(PhysicalMachine pm : Environment.physicalMachines)
		{	
			if(!pm.isRunning())
				continue;
			
			consumption += 350;
			for(VirtualMachine vm : pm.getVirtualMachines())
			{
				consumption += 10;
				consumption += (0.1 * vm.getLoadCPUs());
			}
		}
		
		for(Application app : Environment.federations)
			consumption += (0.4 * app.getLoadCPUs());
		
		return consumption;
	}
	
	private void computeStats()
	{
		cpusOverallRunning = 0;
		cpusMaxUsed = 0;
		pmsRunning = 0;
		
	    // used to determine the pm with lowest max. load
		leastUsedPM = null;
		leastUsedPMcpus = null;
		Integer cpusMaxUsedOnCurrentPm = null;
		
		for(PhysicalMachine pm : Environment.physicalMachines)
		{	
			if(!pm.isRunning())
				continue;
			
			pmsRunning++;
			
			cpusMaxUsedOnCurrentPm = 0;
			for(VirtualMachine vm : pm.getVirtualMachines())
			{
				cpusOverallRunning += vm.getMaxCPUs();
		
				cpusMaxUsed += vm.getMaxloadCPUs() + vm.getReservedCPUs();
				cpusMaxUsedOnCurrentPm += vm.getMaxloadCPUs() + vm.getReservedCPUs();
			}
			
			// determine pm with lowest max. load
			if(leastUsedPM == null || leastUsedPMcpus >= cpusMaxUsedOnCurrentPm)
			{
				leastUsedPM = pm;
				leastUsedPMcpus = cpusMaxUsedOnCurrentPm;
			} 
		}
	}
	
	private float getUsagePercentage(Integer cpusMaxUsed, Integer cpusOverallRunning)
	{
		return ((float) 100 * cpusMaxUsed) / cpusOverallRunning;
	}
	
	private Mode getMode(float usagePercentage)
	{
		if(usagePercentage < ttLow)
			return Mode.GREEN;
		else if(usagePercentage > ttHigh)
			return Mode.RED;
		else 
			return Mode.ORANGE;
	}
	
	private void moveAppsFromFederations()
	{
		for(int i=Environment.federations.size()-1; i>=0; i--)
		{
			Application app = Environment.federations.get(i);
			JMSAppListener.addApplication(app);
		}
	}
	
	private Integer tryToStartPM(Integer cpusMaxUsed, Integer cpusOverallRunning)
	{
		for(PhysicalMachine pm : Environment.physicalMachines)
		{
			if(!pm.isRunning())
			{
				Environment.actionCounter++;
				Environment.powerConsumption += 50.0;
				
				pm.start();
				System.out.println("\nSCHEDULER: started new PM - ID: " + pm.getId());
				cpusOverallRunning = tryToStartVM(cpusMaxUsed, cpusOverallRunning);
				
				float newUsagePercentage = getUsagePercentage(cpusMaxUsed, 
						cpusOverallRunning+400);
				Mode newMode = getMode(newUsagePercentage);
				
				if(newMode == Mode.ORANGE)
					break;
			}
		}
		
		return cpusOverallRunning;
	}
	
	private Integer tryToStartVM(Integer cpusMaxUsed, Integer cpusOverallRunning)
	{
		for(PhysicalMachine pm : Environment.physicalMachines)
		{
			if(!pm.isRunning())
				continue;
			
			while(pm.getFreeCPUs() >= 400)
			{
				float newUsagePercentage = getUsagePercentage(cpusMaxUsed, 
						cpusOverallRunning+400);
				Mode newMode = getMode(newUsagePercentage);
				
				if(newMode == Mode.RED || newMode == Mode.ORANGE)
				{
					Environment.actionCounter++;
					Environment.powerConsumption += 10.0;
					
					VirtualMachine newVM = new VirtualMachine(Environment.getIncrementedVMCounter(), 400);
					pm.addVirtualMachine(newVM);
					
					cpusOverallRunning += 400;
					
					System.out.println("\nSCHEDULER: created new VM - ID: " + newVM.getId());
					
					if(newMode == Mode.ORANGE)
						return cpusOverallRunning;
				} 
				else
					return cpusOverallRunning;
			}
		}
		
		return cpusOverallRunning;
	}
	
	private Integer tryToMoveVM(PhysicalMachine leastUsedPM, Integer cpusMaxUsed, 
			Integer cpusOverallRunning)
	{
		for(int j=leastUsedPM.getVirtualMachines().size()-1; j>=0; j--)
		{
			VirtualMachine vm = leastUsedPM.getVirtualMachines().get(j);
			
			for(PhysicalMachine pm : Environment.physicalMachines)
			{
				if(pm.equals(leastUsedPM) || !pm.isRunning())
					continue;
				
				// move vm
				if(pm.getFreeCPUs() >= vm.getMaxCPUs())
				{
					Environment.actionCounter++;
					Environment.powerConsumption += 5.0;
					
					System.out.println("\nSCHEDULER: moved VM - ID: " +  vm.getId());
					pm.addVirtualMachine(vm);
					break;
				}				
			}
		}
		
		// unchanged --> resources are just moved
		return cpusOverallRunning;
	}
	
	private Integer tryToShutDownVM(PhysicalMachine leastUsedPM, Integer cpusMaxUsed, 
			Integer cpusOverallRunning)
	{
		leastUsedPM.setMaybeTerminated(true);
		
		for(int j=leastUsedPM.getVirtualMachines().size()-1; j>=0; j--)
		{
			VirtualMachine vm = leastUsedPM.getVirtualMachines().get(j);
			
			/* check if the elimination of the vm results in orange or green mode */
			float newUsagePercentage = getUsagePercentage(cpusMaxUsed, 
					cpusOverallRunning-vm.getMaxCPUs());
			Mode newMode = getMode(newUsagePercentage);
			
			if(newMode == Mode.ORANGE || newMode == Mode.GREEN)
			{
				// try to move apps
				boolean appMovingOk = true;
				for(int i=vm.getApplications().size()-1; i>=0 ; i--)
				{
					Application app = vm.getApplications().get(i);
					
					// try to move single app
					if(JMSAppListener.tryToAddApp(app))
					{
						Environment.actionCounter++;
						Environment.powerConsumption += 0.5;
						System.out.println("\nSCHEDULER: moved App - ID: " +  app.getId());
					}
					else
						appMovingOk = false;
				}
				
				if(appMovingOk)
				{
					Environment.actionCounter++;
					Environment.powerConsumption += 7;
					
					System.out.println("\nSCHEDULER: stopped VM - ID: " +  vm.getId());	
					cpusOverallRunning -= vm.getMaxCPUs();
					leastUsedPM.removeVirtualMachine(vm);
				}
				
				if(newMode == Mode.ORANGE)
					break;
			}
		}
		
		leastUsedPM.setMaybeTerminated(false);
		
		return cpusOverallRunning;
	}
	
	private Integer tryToShutDownPM(PhysicalMachine leastUsedPM, Integer cpusMaxUsed, 
			Integer cpusOverallRunning)
	{
		boolean stopPM = false;
		
		// if there are no VMs on the PM --> stop the PM
		if(leastUsedPM.getVirtualMachines().size() == 0)
			stopPM = true;
		
		if(stopPM) {
			Environment.actionCounter++;
			Environment.powerConsumption += 20;
			
			leastUsedPM.stop();
			System.out.println("\nSCHEDULER: stopped PM - ID: " +  leastUsedPM.getId());	
		}
		
		
		return cpusOverallRunning;
	}
}
